/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.sd.chat;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.Vector;
import java.util.logging.Level;
import javax.imageio.ImageIO;

/**
 *
 * @author Old
 */
public class ClientManager extends Thread {

    private ServerData db;
    private ClientHandlerInterface ligacoes;
    private Thread ligacoesThread;
    private boolean isTCP = true;
    private Vector<TCPMsg> receivedQueue;
    private Vector<TCPMsg> tempSendQueue;
    private boolean shutdown = false;
    private boolean shoudManage = false;
    private Vector<String> serverLog;
    private ChatClientView clientView;
    private Logger log = new Logger(true);
    //Connection Info
    private boolean hasConnectionInfo = false;
    private String serverAddress = "127.0.0.1";
    private String backupServerAdress = "127.0.0.1";
    private int serverPort = 7676;
    private int conRetries = 20;
    private boolean isSoftReconnect = false;
    //Auth Info
    private boolean hasAuthInfo = false;
    private boolean isRegistering = false;
    private String username = "";
    private String password = "";
    private Utilizador userInfo;
    private ClientState currState = ClientState.NOTCON;

    public ClientManager(ServerData db, ChatClientView v) {
        serverLog = new Vector<String>();
        receivedQueue = new Vector<TCPMsg>();
        tempSendQueue = new Vector<TCPMsg>();
        clientView = v;
        this.db = db;
    }

    @Override
    public void run() {
        while (!isShutdown()) {
            log.erro("ClientManager running...");
            manageClient();
        }
        log.erro("ClientManager exiting...");
        ligacoes.setShutdown(true);
        /*try {
        log.erro("ClientManager joining...");
        ligacoesThread.join();
        } catch (InterruptedException ex) {
        log.erro("interruptedException caught in JOIN");
        }*/
        log.erro("ClientManager going out...");
    }

    public synchronized void manageClient() {
        //while (!shoudManage) {
        if(conRetries<5&&hasConnectionInfo){
            serverAddress = backupServerAdress;
        }
        if ((receivedQueue.isEmpty() && tempSendQueue.isEmpty() && (currState != ClientState.NOTCON && currState != ClientState.NOTAUTH)) || (currState == ClientState.NOTCON && !hasConnectionInfo) || (currState == ClientState.NOTAUTH && !hasAuthInfo)) {
            try {
                log.outln("ClientManager preparint to wait.");
                wait();
            } catch (InterruptedException e) {
                log.erro("interruptedException caught");
            }
        }
        log.outln("going for manage run: " + currState);
        if (currState == ClientState.NOTCON) { //Não está ligado ainda
            if (hasConnectionInfo) { //Pode ligar porque tem info para ligar
                if (isTCP) {
                    try {
                        ConnectionHandler conHandler = new ConnectionHandler(new Socket(serverAddress, serverPort), 666, this);
                        ligacoes = (ClientHandlerInterface) conHandler;
                        conHandler.start();
                        ligacoesThread = (Thread) conHandler;
                        currState = ClientState.NOTAUTH;
                        clientView.setConnectionAccepted();
                        clientView.displayMsg("ligação estabelecida", 1);
                    } catch (UnknownHostException ex) {
                        if (conRetries > 0) {
                            conRetries--;
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException ex1) {
                                log.erro("trying to sleep");
                            }
                        } else {
                            hasConnectionInfo = false;
                        }
                        log.erro("ClientManager UnknownHostException: " + ex);
                        clientView.displayMsg("servidor não encontrado", 3);
                    } catch (IOException ex) {
                        if (conRetries > 0) {
                            conRetries--;
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException ex1) {
                                log.erro("trying to sleep");
                            }
                        } else {
                            hasConnectionInfo = false;
                        }
                        log.erro("ClientManager IOException: " + ex);
                        clientView.displayMsg("falha na ligação", 3);
                    }
                } else {
                    try {
                        RMIServerToolInterface servertool = (RMIServerToolInterface) Naming.lookup("rmi://" + serverAddress + "/chatserversd");
                        RMIConnectionHandlerCli conHandler = new RMIConnectionHandlerCli(this);
                        RMIHandlerInterface serverHandler = servertool.registerConnection(conHandler);
                        conHandler.setcliCon(serverHandler);
                        ligacoes = (ClientHandlerInterface) conHandler;
                        ligacoesThread = new Thread(conHandler);
                        ligacoesThread.start();
                        currState = ClientState.NOTAUTH;
                        clientView.setConnectionAccepted();
                        clientView.displayMsg("ligação estabelecida", 1);
                    } catch (NotBoundException ex) {
                        if (conRetries > 0) {
                            conRetries--;
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException ex1) {
                                log.erro("trying to sleep");
                            }
                        } else {
                            hasConnectionInfo = false;
                        }
                        log.erro("ClientManager NotBoundException: " + ex);
                        clientView.displayMsg("servidor não tem serviço RMI", 3);
                    } catch (MalformedURLException ex) {
                        if (conRetries > 0) {
                            conRetries--;
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException ex1) {
                                log.erro("trying to sleep");
                            }
                        } else {
                            hasConnectionInfo = false;
                        }
                        log.erro("ClientManager MalformedURLException: " + ex);
                        clientView.displayMsg("servidor não encontrado", 3);
                    } catch (RemoteException ex) {
                        if (conRetries > 0) {
                            conRetries--;
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException ex1) {
                                log.erro("trying to sleep");
                            }
                        } else {
                            hasConnectionInfo = false;
                        }
                        log.erro("ClientManager RemoteException: " + ex);
                        clientView.displayMsg("servidor não encontrado", 3);
                    }
                }
            }
        } else if (currState == ClientState.NOTAUTH) { //Não está autenticado
            if (hasAuthInfo && !isRegistering) { //Tem informações de autenticação
                log.outln("fazer login");
                Utilizador logInfo = new Utilizador(username, password, 0);
                byte[] msgContent = getBytes(logInfo);
                TCPMsg logMsg = new TCPMsg(MsgType.LOGIN, msgContent.length, msgContent);
                receivedQueue.clear();
                ligacoes.addMsg(logMsg);
                currState = ClientState.AUTHSENT;
                clientView.displayMsg("tentar autenticação", 2);
            } else if (hasAuthInfo && isRegistering) {
                log.outln("fazer registo");
                Utilizador logInfo = new Utilizador(username, password, 0);
                byte[] msgContent = getBytes(logInfo);
                TCPMsg logMsg = new TCPMsg(MsgType.REGISTER, msgContent.length, msgContent);
                receivedQueue.clear();
                ligacoes.addMsg(logMsg);
                currState = ClientState.AUTHSENT;
                clientView.displayMsg("tentar registo", 2);
            }
        } else if (currState == ClientState.AUTHSENT && isRegistering) { //Já enviou autenticação
            if (receivedQueue.size() > 0) { //Recebeu alguma coisa
                TCPMsg msgRcved = receivedQueue.remove(0);
                if (msgRcved.type == MsgType.REGISTERACCEPT) {
                    log.outln("registo aceite");
                    currState = ClientState.ISAUTH;
                    clientView.displayMsg("ligado e autenticado", 1);
                    userInfo = (Utilizador) getObjectFromBytes(msgRcved.content);
                    clientView.setEsteUser(userInfo);
                    clientView.setAuthenticated();
                } else if (msgRcved.type == MsgType.REGISTERFAILED) {
                    currState = ClientState.NOTAUTH;
                    hasAuthInfo = false;
                    isRegistering = false;
                    String motivoDaFalha = (String) getObjectFromBytes(msgRcved.content);
                    log.outln("registo falhado: " + motivoDaFalha);
                    clientView.setAuthFailMessage(motivoDaFalha);
                    clientView.displayMsg("registo falhou", 3);
                }
            }
        } else if (currState == ClientState.AUTHSENT && !isRegistering) { //Já enviou autenticação
            if (receivedQueue.size() > 0) { //Recebeu alguma coisa
                TCPMsg msgRcved = receivedQueue.remove(0);
                if (msgRcved.type == MsgType.LOGINACCEPT) {
                    log.outln("login aceite");
                    currState = ClientState.ISAUTH;
                    clientView.displayMsg("ligado e autenticado", 1);
                    userInfo = (Utilizador) getObjectFromBytes(msgRcved.content);
                    clientView.setEsteUser(userInfo);
                    clientView.setAuthenticated();

                    
                    if(isSoftReconnect){
                        isSoftReconnect = false;
                    }
                } else if (msgRcved.type == MsgType.LOGINFAILED) {
                    currState = ClientState.NOTAUTH;
                    hasAuthInfo = false;
                    String motivoDaFalha = (String) getObjectFromBytes(msgRcved.content);
                    log.outln("login falhado: " + motivoDaFalha);
                    clientView.setAuthFailMessage(motivoDaFalha);
                    clientView.displayMsg("autenticação falhou", 3);
                    if(isSoftReconnect){
                        isSoftReconnect = false;
                    }
                }
            }
        } else if (currState == ClientState.ISAUTH) {
            if (receivedQueue.size() > 0) { //Recebeu alguma coisa
                TCPMsg msgRcved = receivedQueue.remove(0);
                MsgType oTipo = msgRcved.type;
                if (oTipo == MsgType.POST) {
                    Post tempPost = (Post) getObjectFromBytes(msgRcved.content);
                    for (int j = 0; j < db.getPosts().size(); j++) {
                        if (db.getPosts().get(j).getId() == tempPost.getId()) {
                            db.getPosts().remove(j);
                        }
                    }
                    db.getPosts().add(tempPost);
                    Collections.sort(db.getPosts());
                    clientView.updatePosts(db);
                } else if (oTipo == MsgType.COMMENT) {
                    Comment tempCom = (Comment) getObjectFromBytes(msgRcved.content);
                    db.getComments().add(tempCom);
                    Collections.sort(db.getComments());
                    clientView.updatePosts(db);
                } else if (oTipo == MsgType.EDIT) {
                } else if (oTipo == MsgType.MESSAGE) {
                    Message tempMess = (Message) getObjectFromBytes(msgRcved.content);
                    db.getPms().add(tempMess);
                    Collections.sort(db.getPms());
                    clientView.updatePMs(db);
                } else if (oTipo == MsgType.ELIMINATEPOST) {

                    Integer tempId = (Integer) getObjectFromBytes(msgRcved.content);
                    for (int i = 0; i < db.getPosts().size(); i++) {
                        if (db.getPosts().get(i).getId() == tempId) {
                            for (int j = 0; j < db.getComments().size(); j++) {
                                if (db.getComments().get(j).getPostId() == tempId) {
                                    db.getComments().remove(j);
                                }
                            }
                            if (db.getPosts().get(i).isHaspic()) {
                                File tempPic = new File(db.getPosts().get(i).getPic());
                                if (!tempPic.delete()) {
                                    tempPic.deleteOnExit();
                                }
                            }
                            db.getPosts().remove(i);
                            clientView.updatePosts(db);
                            break;
                        }
                    }


                    //Collections.sort(db.getPms());
                } else if (oTipo == MsgType.ELIMINATECOMMENT) {

                    Integer tempId = (Integer) getObjectFromBytes(msgRcved.content);
                    for (int j = 0; j < db.getComments().size(); j++) {
                        if (db.getComments().get(j).getId() == tempId) {
                            db.getComments().remove(j);
                            clientView.updatePosts(db);
                            break;
                        }
                    }




                    //Collections.sort(db.getPms());
                } else if (oTipo == MsgType.PICSEND) {
                    try {
                        PicSent picMsg = (PicSent) getObjectFromBytes(msgRcved.content);
                        BufferedImage image = ImageIO.read(new ByteArrayInputStream(picMsg.picdata));
                        ImageIO.write(image, "jpeg", new File(picMsg.picName));
                        addMessageToLog("ClientManager recebi e gravei Imagem: " + picMsg.picName);
                    } catch (FileNotFoundException ex) {
                        log.erro("ClientManager erro na imagem que chegou FILENOTFOUND.");
                    } catch (IOException ex) {
                        log.erro("ClientManager erro na imagem que chegou.");
                    }
                } else if (oTipo == MsgType.ONLINEUSERS) {
                    UserList tempMess = (UserList) getObjectFromBytes(msgRcved.content);
                    clientView.updateUsers(tempMess);
                }
            }else if(tempSendQueue.size()>0){
                log.outln("ClientManager ha mensagens na tempQueue");
                ligacoes.addMsg(tempSendQueue.remove(0));
            }
        }
        
           /*if(currState == ClientState.AUTHSENT||currState == ClientState.ISAUTH){
            boolean check = ligacoes.canAddMsg();
            }*/

        
        shoudManage = false;
        //}
    }

    public synchronized void setConnectionInfo(String address, String backupaddress, boolean isRMI) {
        serverAddress = address;
        backupServerAdress = backupaddress;
        hasConnectionInfo = true;
        this.isTCP = !isRMI;
        shoudManage = true;

        notify();
    }

    public synchronized void setAuthInfo(String username, String password, boolean register) {
        this.username = username;
        this.password = password;
        this.isRegistering = register;
        shoudManage = true;
        this.hasAuthInfo = true;
        notify();
    }

    /*public synchronized void newConnection(ConnectionHandler newCon) {
    ligacoes = newCon;
    connected = true;
    serverLog.add("connection established");
    }*/
    public synchronized void newMessageReceived(TCPMsg newMsg) {
        receivedQueue.add(newMsg);
        log.outln("ClientManager MsgRcv: " + newMsg);
        shoudManage = true;
        notify();
    }

    public synchronized void connectionLost() {
        this.tempSendQueue.removeAllElements();
        this.tempSendQueue.addAll(ligacoes.getSendQueue());
        log.outln("ClientManager CONNECTION LOST");
        if (currState == ClientState.ISAUTH){
            isSoftReconnect = true;
        }
        currState = ClientState.NOTCON;
        conRetries = 20;
        shoudManage = true;
        notifyAll();
    }

    public synchronized void disconnectAndClearAll() {
        ligacoes.setShutdown(true);
        hasAuthInfo = false;
        hasConnectionInfo = false;
        db = new ServerData();
        clientView.setDisconnected();
        clientView.displayMsg("desligado", 0);
        currState = ClientState.NOTCON;
        shoudManage = true;
        notifyAll();
    }

    public synchronized void shutdown() {
        this.setShutdown(true);
    }

    public boolean isShutdown() {
        return shutdown;
    }

    public synchronized void setShutdown(boolean shutdown) {
        this.shutdown = shutdown;
        this.interrupt();
        notifyAll();
    }

    public Vector<String> getServerLog() {
        return serverLog;
    }

    public void setServerLog(Vector<String> serverLog) {
        this.serverLog = serverLog;
    }

    public synchronized void addMessageToLog(String msg) {
        this.serverLog.add(msg);
        notify();
    }

    public synchronized boolean hasMessagesToDisplay() {
        if (this.serverLog.size() > 0) {
            return true;
        }
        return false;
    }

    public synchronized String getMessageToDisplay() {
        return this.serverLog.remove(0);
    }

    public synchronized void dispatchToGUI() {
        while (!this.hasMessagesToDisplay()) {
            try {
                wait();
            } catch (InterruptedException e) {
                log.erro("dispatchToGUI interruptedException caught");
            }
            while (this.hasMessagesToDisplay()) {
                log.outln(this.getMessageToDisplay());
            }
            notify();
        }
    }

    public ClientHandlerInterface getLigacoes() {
        return ligacoes;
    }

    public void setLigacoes(ClientHandlerInterface ligacoes) {
        this.ligacoes = ligacoes;
    }

    public synchronized void newSimplePost(String postContent) {
        Post tempPost = new Post(postContent, 3, this.userInfo);
        byte[] msgContent = getBytes(tempPost);
        TCPMsg msg = new TCPMsg(MsgType.POST, msgContent.length, msgContent);
        addMessageToLigacao(msg);
    }

    public synchronized void newPost(String texto, boolean hasPic, String picLocation, GregorianCalendar postDate) {
        String picName = "";
        if (hasPic) {
            try {
                File picFile = new File(picLocation);
                picName = picFile.getName();
                BufferedImage pic = ImageIO.read(picFile);
                BufferedImage picResize = picUtils.resizePic(pic, 300, 200);

                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ImageIO.write(picResize, "jpeg", baos);
                baos.flush();
                byte[] picInfo = baos.toByteArray();
                baos.close();
                PicSent picMsg = new PicSent(picInfo, picName);
                byte[] picMsgBytes = getBytes(picMsg);
                TCPMsg msg = new TCPMsg(MsgType.PICSEND, picMsgBytes.length, picMsgBytes);
                addMessageToLigacao(msg);
            } catch (IOException ex) {
                log.erro("ClientManager erro a fazer load a foto");
            }
        }
        Post tempPost = new Post(texto, userInfo.getId(), userInfo, hasPic, picName, postDate);
        byte[] msgContent = getBytes(tempPost);
        TCPMsg msg = new TCPMsg(MsgType.POST, msgContent.length, msgContent);
        addMessageToLigacao(msg);
    }

    public synchronized void editSubmit(String texto, int postId) {
        Post tempPost = new Post(texto, postId, userInfo);
        byte[] msgContent = getBytes(tempPost);
        TCPMsg msg = new TCPMsg(MsgType.EDIT, msgContent.length, msgContent);
        addMessageToLigacao(msg);
    }

    public synchronized void newCommentSubmit(String texto, int postId) {
        Comment tempCom = new Comment(texto, postId, userInfo, postId, new GregorianCalendar());
        byte[] msgContent = getBytes(tempCom);
        TCPMsg msg = new TCPMsg(MsgType.COMMENT, msgContent.length, msgContent);
        addMessageToLigacao(msg);
    }

    public synchronized void newMessageSubmit(Message mess) {
        byte[] msgContent = getBytes(mess);
        TCPMsg msg = new TCPMsg(MsgType.MESSAGE, msgContent.length, msgContent);
        addMessageToLigacao(msg);
    }

    public synchronized void eliminateSubmit(int id, boolean post) {//false = comment
        Integer idToEliminate = id;
        byte[] msgContent = getBytes(idToEliminate);
        TCPMsg msg;
        if (post) {
            msg = new TCPMsg(MsgType.ELIMINATEPOST, msgContent.length, msgContent);
        } else {
            msg = new TCPMsg(MsgType.ELIMINATECOMMENT, msgContent.length, msgContent);
        }
        addMessageToLigacao(msg);
    }
    
    public synchronized void addMessageToLigacao(TCPMsg msg){
        if(isSoftReconnect){
            this.tempSendQueue.add(msg);
            log.outln("Client Manager Added message to tempQueue: "+msg);
        }else {
            if(ligacoes.canAddMsg()){
                log.outln("Client Manager Added message to ligacao: "+msg);
                ligacoes.addMsg(msg);
            }else {
                this.tempSendQueue.add(msg);
                log.outln("Client Manager Added message to tempQueue: "+msg);
            }
        }
    }

    public byte[] getBytes(Serializable obj) {
        byte[] yourBytes;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutput out = null;
        try {
            out = new ObjectOutputStream(bos);
            out.writeObject(obj);
            yourBytes = bos.toByteArray();
            out.close();
            bos.close();
            return yourBytes;
        } catch (IOException e) {
        }
        return new byte[0];
    }

    public Object getObjectFromBytes(byte[] yourBytes) {
        Object retorno;
        ByteArrayInputStream bis = new ByteArrayInputStream(yourBytes);
        ObjectInput in = null;
        try {
            in = new ObjectInputStream(bis);
            retorno = in.readObject();
            bis.close();
            in.close();
            return retorno;
        } catch (ClassNotFoundException e) {
        } catch (IOException e) {
        }

        return new Object();
    }

    public synchronized Utilizador getUserInfo() {
        return userInfo;
    }

    public synchronized void setUserInfo(Utilizador userInfo) {
        this.userInfo = userInfo;
    }
    
    
}
